package testGenerator.jUnitCreator;

import gui.testSetup.components.TestConfSetting;
import java.util.ArrayList;
import testGenerator.Controller;
import testGenerator.testMethodModel.UserParamTestValue;
import testGenerator.testMethodModel.TestPath;
import testGenerator.testMethodModel.AutoJUnitMethod;
import testGenerator.testMethodModel.GenericTestPoint;
import testGenerator.testMethodModel.TestCondition;

/**
 *
 * @author William Whitney
 */
public class JUnitTestCreator
{

    private final Controller controller;
    private ArrayList<AutoJUnitMethod> methods;
    private final boolean printStubs;

    public JUnitTestCreator(Controller controller, AutoJUnitMethod currentMethod, boolean printStubs)
    {
        this.controller = controller;
        methods = new ArrayList<AutoJUnitMethod>();
        methods.add(currentMethod);
        this.printStubs = printStubs;

    }

    public JUnitTestCreator(Controller controller, ArrayList<AutoJUnitMethod> currentMethods, boolean printStubs)
    {
        this.controller = controller;
        methods = new ArrayList<AutoJUnitMethod>();
        methods.addAll(currentMethods);
        this.printStubs = printStubs;
    }

    public String getTest()
    {
        String test = "";

        test += getPackageAndImports();

        test += "public class " + this.controller.getClassName() + "Test\n";
        test += "{\n";

        test += getTestHeader(this.controller.getSetupInfo(TestConfSetting.TEST_HEADER));
        test += "\n";
        test += getTestSetup(this.controller.getSetupInfo(TestConfSetting.SET_UP_METHOD));
        test += getTestTearDown(this.controller.getSetupInfo(TestConfSetting.TEAR_DOWN_METHOD));

        for (AutoJUnitMethod method : this.methods)
        {
            test += getMethodTest(method, methods.indexOf(method));
        }

        test += "}\n";
        return test;
    }

    private String getMethodTest(AutoJUnitMethod method, int methodNum)
    {
        String testStr = "";

        int branchNum = 0;
        for (TestPath branch : method.testBranches)
        {
            if (branch.isComplete(method) || printStubs)
            {
                testStr += getComment(branch);
                testStr += "\t@Test\n";
                testStr += "\tpublic void ";
                testStr += "test_method_" + method.name + "_" + methodNum + "_branch_" + branchNum + "()\n";
                testStr += "\t{\n";
                testStr += getTestBody(method, branch, branchNum);
                testStr += "\n\t}\n\n";
            }
            branchNum++;
        }

        return testStr;

    }

    private String getTestBody(AutoJUnitMethod method, TestPath branch, int branchNum)
    {
        String testBody = "";

        String testPrintout = "Now Testing Method:" + method.name + " Branch:" + branchNum;
        testBody += "System.out.println(\"" + testPrintout + "\");\n";
        testBody += getConstructor(method, branch);
        testBody += getAdditionalTestConstraints(branch);

        if (method.isConstructor())
        {
            //Do nothing
        }
        else if (method.isVoidMethod())
        {
            testBody += "\n//Call Method\n";
            testBody += getMethodCall(method, branch);
        }
        else
        {
            testBody += "\n//Get expected result and result\n";
            if (branch.testMethod.isAllowsTwoArgs())
            {
                testBody += "Object expResult = " + branch.expectedOutput + ";\n";
            }
            
            testBody += "Object result = " + getMethodCall(method, branch);

            testBody += "\n//Check Return value\n";
            if (branch.testMethod.isAllowsTwoArgs())
            {
                testBody += branch.testMethod.getMethodName()+"(expResult, result);\n";
            }
            else 
            {
                testBody += branch.testMethod.getMethodName()+"(result);\n";
            }
        }
        testBody += getAdditionalVerificationPoints(branch);
        testBody = addTabs(testBody, 2);

        return testBody;
    }

    private String getMethodCall(AutoJUnitMethod method, TestPath branch)
    {
        String methodCall = "";
        if (method.hasModifier("static"))
        {
            methodCall = controller.getClassName() + "." + getMethodStr(method, branch);
            methodCall += "\n";
        }
        else
        {
            methodCall = "instance." + getMethodStr(method, branch);
            methodCall += "\n";
        }
        return methodCall;

    }

    private String getConstructor(AutoJUnitMethod method, TestPath branch)
    {
        String constructor = "";
        if (method.isConstructor())
        {
            constructor += "\n//Constructor\n";
            constructor += controller.getClassName() + " instance = new " + this.getMethodStr(method, branch);
            constructor += "\n";
        }
        else if (!method.hasModifier("static"))
        {
            constructor += "\n//Constructor\n";
            constructor += controller.getClassName() + " instance = " + branch.constructor;
            constructor += "\n";
        }
        return constructor;
    }

    private String getAdditionalVerificationPoints(TestPath branch)
    {
        String str = "";

        if (branch.genericTestPoints.size() > 0)
        {
            str += "\n//Check Test Verification Points\n";
        }
        for (GenericTestPoint point : branch.genericTestPoints)
        {

            String expectedResult = point.expectedResult;
            String result = "instance." + point.instanceManipulation;
            
            if (point.chosenMethod.isAllowsTwoArgs())
            {
                str += point.chosenMethod.getMethodName()+"(" + expectedResult + ", " + result + ");\n";
            }
            else
            {
                str += point.chosenMethod.getMethodName()+"(" + result + ");\n";
            }

        }
        return str;
    }

    private String getAdditionalTestConstraints(TestPath branch)
    {
        String str = "";

        if (!branch.additionalConstraints.equals(""))
        {
            str += "\n//Add additional test constraints\n";
            str += branch.additionalConstraints;
            str += "\n";
        }

        return str;

    }

    private String getComment(TestPath testBranch)
    {
        String comment = "";

        comment += "\t/*\n";
        comment += "\t * Testing Conditon(s): ";

        for (TestCondition cond : testBranch.conditions)
        {
            comment += cond.getCondition() + ", ";
        }
        comment = comment.substring(0, comment.length() - 2);
        comment += "\n";
        comment += "\t */\n";

        return comment;
    }

    private String getTestHeader(String setup)
    {
        return this.addTabs(setup, 1);
    }

    private String getTestSetup(String setup)
    {
        String result = "";

        setup = this.addTabs(setup, 2);

        result += "\n";
        result += "\t@Before\n";
        result += "\tpublic void setUp()\n";
        result += "\t{\n";
        result += setup + "\n";
        result += "\t}\n";

        return result;
    }

    private String getTestTearDown(String setup)
    {
        String result = "";
        setup = this.addTabs(setup, 2);

        result += "\n";
        result += "\t@After\n";
        result += "\tpublic void tearDown()\n";
        result += "\t{\n";
        result += setup + "\n";
        result += "\t}\n\n";

        return result;
    }

    private String getPackageAndImports()
    {
        String imports = "";


        imports += "import org.junit.After;\n";
        imports += "import org.junit.Before;\n";
        imports += "import org.junit.Test;\n";
        imports += "import static org.junit.Assert.*;\n\n";

        return imports;
    }

    private String addTabs(String text, int numTabs)
    {
        String tabOffset = "";

        for (int i = 0; i < numTabs; i++)
        {
            tabOffset += "\t";
        }
        text = tabOffset + text;
        text = text.replaceAll("\n", "\n" + tabOffset);
        return text;
    }

    private String getMethodStr(AutoJUnitMethod method, TestPath branch)
    {
        String methodStr = "";
        methodStr += method.name + "(";

        for (UserParamTestValue param : branch.userParamTestValues)
        {
            methodStr += param.userParamValue;
            methodStr += ", ";
        }
        if (branch.userParamTestValues.size() > 0)
        {
            methodStr = methodStr.substring(0, methodStr.length() - 2);
        }
        methodStr += ");";

        return methodStr;
    }
}
